//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   PACKAGE   : dcf
//   FILE      : LocationServiceBehavior.java
//   CLASS     : LocationServiceBehavior
//   DATE      : 2006-04-01
//   
//   DESCRIPTION
//   The LocationServiceBehavior is the behavior of the location service. It generally is repsonsible for 
//   accepting registration and deregistration messages from components, maintaining a current list of 
//   publishers and subscribers and sending notifications to endpoints when connections are established or
//   terminated.
//   
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   Copyright (c) 2006 by Hal Smith
//
//   Permission is hereby granted, free of charge, to any person obtaining a copy of this source code 
//   (the "Code")to deal with the Code without restriction, including without limitation the rights to use, copy,
//   modify, merge, publish and distribute the Code, and to permit persons to whom the Code is furnished to do 
//   so, subject to the following conditions:
//
//   The above copyright notice and this permission notice shall be included in all copies or substantial 
//   portions of the Software.
//   
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   VERSION HISTORY
//
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

package dcf;

import java.util.Hashtable;


class LocationServiceBehavior extends Behavior {
	
	// The behavior to use when sending messages.
	//
	private IOutgoingBehavior outgoingBehavior;
	
	// Publisher and subscriber hash tables that are indexed by the service name. There may be only one 
	// publisher of a given service but multiple subscribers.
	//
	private Hashtable<String, Publisher>      publisherHash;
	private Hashtable<String, SubscriberList> subscriptionsHash;

	
	// Constructor.
	//
	// Initializes the instance of the behavior. There are no exceptions thrown by this operation.
	//
	public LocationServiceBehavior() throws InitializationException {
		
		try {
			
			publisherHash     = new Hashtable<String, Publisher>();
			subscriptionsHash = new Hashtable<String, SubscriberList>(); 
		
		} catch (Exception e) {
			
			System.out.println("LocationServiceBehavior.LocationServiceBehavior(initEndpoint, initServiceName):");
			System.out.println("   Unexpected exception creating the publisher or subscriber hashtable." 
					           + e.toString());			
			
			throw new InitializationException();
			
		}
	
	} // constructor


	// This operation handles the incoming messages. The messages expected and associated actions are as
	// follows
	//
	//    SubscribeRequest  - Add the subscriber to the list of subscribers for that service. If a publisher
	//                        is known, send a SubscriberMessage to the publisher and a PublisherMessage to
	//                        the subscriber indicating an connection has been made. A component can register
	//                        register as a subscriber more than once.
	// 
	//    PublishRequest    - Add the publisher to the list of oublishers. If any subscribers are known, send
	//                        a SubscriberListMessage to the publisher with all of the known subscribers and a 
	//                        PublisherMessage to each of the subscribers indicating an connection has been
	//                        made. If a publisher already exists, the request is denied and an error will
	//						  be logged. Note that the requesting publisher will get no feedback. It will 
	// 						  behave as if it has no subscribers.
	//
	//    DeregisterMessage - Remove the component from the appropriate list and if a connection has been 
	//						  previously established, send a disconnect message to the appropriate components.
	//						  That is, if a publisher deregisters, inform all subscribers; if a subscriber 
	//						  deregisters, inform the publisher.
    //
	// There are no exceptions thrown by this operation.
	// 
	@Override
	public void interpretMessage(Message message)
	{
		Publisher  pub = null;
		Subscriber sub = null;
		
		// Ensure that there are no exceptions being thrown from the operation.
		//
		try {

			// Handle the incoming message. It should be one the following:
			//
			//    SubscribeRequest
			//    PublishRequest
			//    DeregisterMessage
			//
			// If not, we log the arrival of an unexpected message.
			//
			// 
			// SUBSCRIBER REQUEST
			//
			if(message instanceof SubscribeRequest) {
				
				// Store the message in a more friendly accessor and retrieve the subscriber
				// information for insertion into the list.
				//
				SubscribeRequest request = (SubscribeRequest)message;
				
				sub = new Subscriber(request.getSenderEndpoint().getHost(),
					                 request.getSenderEndpoint().getPort(),
					                 request.getServiceName());
				
				
				// DEBUG 
				// 
				System.out.println("LocationServiceBehavior.interpretMessage(message):");
				System.out.println("   Received a SubscribeRequest: " + sub.getServiceName());
	            //
				// END DEBUG
				
				
				// Add the subscriber to the appropriate subscriber list. Create the list if it does
				// not already exist.
				//
				if (subscriptionsHash.get(request.getServiceName()) == null) {
					
					subscriptionsHash.put(request.getServiceName(), new SubscriberList());
					
				}
				
				subscriptionsHash.get(request.getServiceName()).add(sub);
				
	
				// Look for a publisher of the service. If one is found, send messages to both the subscriber
				// and publisher indicating a connection has been made.
				//
				pub = publisherHash.get(request.getServiceName());
					
				if(pub != null) {
					
					outgoingBehavior.sendTo(pub, new SubscriberMessage(sub));
					
					outgoingBehavior.sendTo(sub, new PublisherMessage(pub));
														
				} 
			
				
			//
			// PUBLISH REQUEST
			//
			} else if(message instanceof PublishRequest) {
				
				// Store the message in a more friendly accessor and retrieve the publisher
				// information for insertion into the list.
				//
				PublishRequest request = (PublishRequest)message;
				
				pub = new Publisher(request.getSenderEndpoint().getHost(),
				                    request.getSenderEndpoint().getPort(),
					                request.getServiceName());
				

				// DEBUG 
				// 
				System.out.println("LocationServiceBehavior.interpretMessage(message):");
				System.out.println("   Received a PublishRequest: " + pub.getServiceName());
				//  
				// END DEBUG
	
				
				// Look for a current publisher. If no publisher exists, add the publisher to the publisher list
				// for the service. If there are any registered subscribers, send the subscriber info list to 
				// the publisher and the publisher info to each of the subscribers.
				//
				// If a publisher already exists for the service, the second registration is considered an
				// error. The first publisher should be deregistered before the second is registered. Log an
				// error and ignore the request.
				// 
				Publisher currentPub = publisherHash.get(request.getServiceName());
				
				if (currentPub == null) {
		
					publisherHash.put(request.getServiceName(), pub);
					
					SubscriberList subs = subscriptionsHash.get(request.getServiceName());
										
					if (subs != null) {
										
						outgoingBehavior.sendTo(pub, new SubscriberListMessage(subs));
						
						for(int i=0; i<subs.getSubscriptionCount(); ++i) {
		
							outgoingBehavior.sendTo(subs.getSubscriber(i), new PublisherMessage(pub));
						}
		
					}
					
				} else {
					
					System.out.println("LocationServiceBehavior.interpretMessage():");
					System.out.println("   Attempted to register a publisher for a service with a publisher. Ignoring request.");
					System.out.println("   Service name                  : " + request.getServiceName());
					System.out.println("   Current publisher endpoint    : " + currentPub.getHost() + ":" + currentPub.getPort());					
					System.out.println("   Requesting publisher endpoint : " + pub.getHost() + ":" + pub.getPort());					
					
				} // if (currentPub == null) ...
			
			
			//
			// DEREGISTER MESSAGE
			//
			// TODO: Clean up. It's pretty nasty.
			//
			} else if (message instanceof DeregisterMessage) {
				
				// A flag to indicate whether or not we have located the service in the publisher or 
				// subscriber list.
				//
				boolean locatedService = false;
				
				
				// The endpoint of the deregisterer and the service name.
				//  
				Endpoint sender      = message.getSenderEndpoint();
				String   serviceName = ((DeregisterMessage)message).getServiceName();
				
				// Obtain the publisher and subscribers associated to the service name.
				//
				Publisher      publisher      = publisherHash.get(serviceName);
				SubscriberList subscriberList = subscriptionsHash.get(serviceName);
				
				
				// DEBUG 
				// 
				System.out.println("LocationServiceBehavior.interpretMessage(message):");
				System.out.println("   Received a DeregisterMessage: " + serviceName);
				//  
				// END DEBUG

			
				
				// If there is a publisher associated to the service name, ensure the deregistration request
				// came from the actual publisher by examining the endpoint. If so, remove the entry from the 
				// list and send a disconnect message to any subscribers. 
				//
				if (publisher != null) {
					
					if ( (publisher.getPort() == sender.getPort()) && 
						 (publisher.getHost().equals(sender.getHost()))    ) {
						
						// Set the flag indicating that the service was found.
						//
						locatedService = true;
						
						publisherHash.remove(serviceName);
					
						if (subscriberList != null) {
							
							DisconnectMessage dm = new DisconnectMessage(publisher, serviceName);
				
							for (int s=0; s<subscriberList.getSubscriptionCount(); ++s) {
								outgoingBehavior.sendTo(subscriberList.getSubscriber(s), dm);
							}
							
						}
	
					}
									
				} // if (publisher != null) ...
				
				
				// Now check the subscribers - assuming the deregisterer was not the publisher. If the 
				// deregisterer is among the subscribers, remove it. Otherwise, ignore the request.
				//
				if (subscriberList != null && !locatedService) {
									
					// The subscriber.
					//
					Subscriber subscriber = null;
					
					// Look for the deregisterer. If we find it, set the flag to true.
					// 
					for (int s=0; (s<subscriberList.getSubscriptionCount()) && (!locatedService); ++s) {
	                    
						if ( (subscriberList.getSubscriber(s).getPort() == sender.getPort()) && 
						     (subscriberList.getSubscriber(s).getHost().equals(sender.getHost()))  ) {

	                    	subscriber     = subscriberList.getSubscriber(s);
	                    	
							locatedService = true;
					    
	                    } 
					
					}
	                
					
					// If we located the service, we need to send a disconnect message to the publisher.
					//
					if (locatedService) { 	
	
						subscriberList.remove(subscriber);
	
						if (publisher != null) {
			                	
							DisconnectMessage dm = new DisconnectMessage(subscriber, serviceName);
							
							outgoingBehavior.sendTo(publisher, dm);
								
			            }
								                  
					}
					
				} // if (subscriberList != null && !locatedService) ... 
				
				
				// If we haven't found the service, the deregistration request must have been a mistake - 
				// or we have a big problem. In any event, log an error.
				//
				if (!locatedService) {
	
					System.out.println("LocationServiceBehavior.interpretMessage():");
					System.out.println("   Attempt to deregister a service that was never registered.");
					System.out.println("   Service name : " + serviceName);
					System.out.println("   Endpoint     : " + sender.getHost() + ":" + sender.getPort());
				
				} 
								
			} // if (message instanceof DeregisterMessage) ...
		
			
		} catch (Exception e) {
			
			System.out.println("LocationServiceBehavior.interpretMessage():");
			System.out.println("   Unexpected exception occurred: " + e.toString());
			
		} // try-catch

		
	} // interpretMessage()
	
	
	// This operation updates the behavior to use the specified outgoing behavior for sending messages. There
	// are no exceptions thrown by this operation.
	//
	@Override
	public void setOutgoingBehavior (IOutgoingBehavior newOutgoingBehavior) {
		
		outgoingBehavior = newOutgoingBehavior;
		
	} // setOutgoingBehavior()
	
	
} // LocationServiceBehavior
